#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "errlib.h"
#include "named_pipes.h"
#include "transport_layer.h"

/* size of client fifo name buffer */
#define PIPE_NAME	100
/* internal buffer size */
#define BUFFER_SIZE	512

/* fifo creation permissions */
#define FIFO_PERMS	0666
/* fifo open flags */
#define FIFO_FLAGS	O_RDWR

static int isServer;
/* static server named pipe file descriptor variable */
static int fd_server;
/* static client named pipe file descriptor variable */
static int fd_client;
/* client pid */
static int client_pid;
/* client named pipe path and name */
static char client_pipe[PIPE_NAME + 1];


/* convert int to string */
static void itoa(int i, char * string);
/* make "fifo<pid>.tmp" string */
static void pidToPipeName(void);
static int sendDataServer(void * data, int size);
static int sendDataClient(void * data, int size);
static int recvDataServer(void * data);
static int recvDataClient(void * data);
static void init_client(void);
static void init_server(void);
static void close_client(void);
static void close_server(void);

/* send data through named pipe */
int 
sendData(void * data, int size)
{
	if(isServer)
		return sendDataServer(data, size);
	else
		return sendDataClient(data, size);

}

/* receive data through named pipe */
int
recvData(void * data)
{
	if(isServer)
		return recvDataServer(data);
	else
		return recvDataClient(data);
}

/* init named pipes configuration */
void 
init(int isThisTheServer)
{
 	isServer = isThisTheServer;
	
	if(isServer)
	{
		atexit(close_server);
		init_server();
	}
	else
	{
		atexit(close_client);
		init_client();
	}
}


/* 
**********************
** Auxiliar functions
**********************
*/

static int
sendDataServer(void * data, int size)
{
	unsigned char n;
	char buffer[BUFFER_SIZE];
	int ret;

 	/* make "fifo<pid>.tmp" string, client pipe name */ 
	pidToPipeName();

	/* open client named pipe */
	if ((fd_client = open(client_pipe, FIFO_FLAGS)) < 0)
	{
		warn("Server: Unknown error while opening client fifo");
		return -1;
	}

	/* add size at the beginning of data */
	n = (unsigned char) size;
	*buffer = n;
	memcpy(buffer + 1, data, size);

	/* write data */
	if ((ret = write(fd_client, buffer, size + 1)) < 0)
	{
		warn("Server: Error while sending information");
		return -1;
	}	

	/* close client named pipe */
	close(fd_client);
	return ret;
}

static int
sendDataClient(void * data, int size)
{
	unsigned char newSize;
	int ret;
	char buffer[BUFFER_SIZE];

	/* open server named pipe */
	if ((fd_server = open(SERVER_FIFO, FIFO_FLAGS)) < 0)
		fatal("Client: Unknown error while opening server fifo");

	newSize = (unsigned char) size;

	/* write pid */
	memcpy(buffer, &client_pid, sizeof(client_pid));
	/* write size of data */
	memcpy(buffer + sizeof(client_pid), &newSize, 1);
	/* write data */
	memcpy(buffer + sizeof(client_pid) + 1, data, size);

	if ((ret = (write(fd_server, buffer,
		size + sizeof(client_pid) +1) - (sizeof(client_pid) + 1))) < 0)
		fatal("Client: Error while sending information");
	
	close(fd_server);
	return ret;
}

static int
recvDataServer(void * data)
{
	unsigned char n;
	int ret;

	/* read pid */
	if (read(fd_server, &client_pid, sizeof(client_pid)) < 0)
	{
		warn("Server: Error while receiving information");
		return -1;
	}
	/* read size of data */
	if (read(fd_server, &n, 1) < 0 )
	{
		warn("Server: Error while receiving information");
		return -1;
	}
	/* read data */
	if ((ret = read(fd_server, data, n)) < 0)
	{
		warn("Server: Error while receiving information");
		return -1;
	}	
	return ret;
}

static int
recvDataClient(void * data)
{
	unsigned char n;
	int ret;	

	/* read size of data*/
	if ((ret = read(fd_client, &n, 1)) < 0)
		fatal("Client: Error while receiving information");

	/* read data */
	if ((ret = read(fd_client, data, n)) < 0)
		fatal("Client: Error while receiving information");

	return ret;
}

static void
init_client(void)
{
	/* make "fifo<pid>.tmp" string, pipe name */ 
	client_pid = getpid();
	pidToPipeName();
	
	/* make client named pipe */
	if (mkfifo(client_pipe, FIFO_PERMS) == -1)
		if (errno != EEXIST)
			fatal("Client: Unknown error while initializing "
				"the transport protocol");

	/* open client named pipe */
	if ((fd_client = open(client_pipe, FIFO_FLAGS)) < 0)
		fatal("Client: Unknown error while opening client fifo");
}

static void
close_client(void)
{
	/* close client named pipe */
	close(fd_client);

	/* remove client named pipe */ 
	remove(client_pipe);
}

static void
init_server(void)
{
	if(mkfifo(SERVER_FIFO, FIFO_PERMS) == -1)
	{
		if (errno != EEXIST)
			fatal("Server: Unknown error while initializing "
				"the transport protocol");
	}

	if ((fd_server = open(SERVER_FIFO, FIFO_FLAGS)) < 0)
		fatal("Server: Unknown error while opening fifo");

}

static void
close_server(void)
{
	close(fd_server);
	remove(SERVER_FIFO);
}

/* convert int to string */
static void
itoa(int i, char * string)
{
	int power, j;
	
	j = i;

	for (power = 1; j >= 10; j /= 10)
		power *= 10;

	for (; power > 0; power /= 10)
	{
		*string++ = '0' + i/power;
		i %= power;
	}

	*string = '\0';
}

/* make "fifo<pid>.tmp" string */
static void
pidToPipeName(void)
{
	char aux[PIPE_NAME + 1];

	strncpy(client_pipe, FIFO_PREFIX, PIPE_NAME);
	itoa(client_pid, aux);
	strcat(client_pipe, aux);
	strcat(client_pipe, FIFO_SUFFIX);
}
